package net.ifilm.servlet;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public final class VerifyCodeServlet extends HttpServlet
{
	private static final long serialVersionUID = 1L;
	private static SecureRandom g_oRandom = new SecureRandom();
    private static int iMinFontSize = 25;
    private static int iMaxFontSize = 35;
    private static int iDistance = 5;
    private int frameColor = -1;

    private double[] scaleRange = { 0.6D, 0.8D };
    private double[] rotateRange = { -0.2D, 0.2D };

    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        processRequest(req, resp);
    }

    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        processRequest(req, resp);
    }

    private void processRequest(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("image/jpeg");
        resp.setHeader("Pragma", "No-cache");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setDateHeader("Expires", 0L);
        HttpSession session = req.getSession();

        String dictionary = "12345689ABCDEFGHJKMNPQRSUVXYWZ";
        String sRand = generateCodeString(dictionary);

        BufferedImage image = generateCheckCodeImage(sRand);
        session.setAttribute("verifyCode", sRand);

        ServletOutputStream responseOutputStream = resp.getOutputStream();
        ImageIO.setUseCache(false);
        ImageIO.write(image, "JPEG", responseOutputStream);
        responseOutputStream.flush();
        responseOutputStream.close();
    }

    private String generateCodeString(String dictionary) {
        char[] dictionaryChars = dictionary.toCharArray();
        int index1 = (int)(dictionaryChars.length * Math.random());
        int index2 = (int)(dictionaryChars.length * Math.random());
        int index3 = (int)(dictionaryChars.length * Math.random());
        int index4 = (int)(dictionaryChars.length * Math.random());
        char[] codeChars = { dictionaryChars[index1], dictionaryChars[index2], dictionaryChars[index3], dictionaryChars[index4] };

        return String.valueOf(codeChars, 0, codeChars.length);
    }

    private BufferedImage generateCheckCodeImage(String code) {
        char[] codeChars = code.toCharArray();
        BufferedImage[] images = new BufferedImage[codeChars.length];
        Random random = new Random();

        Color fgColor = GetRandColor(10, 150);
        for (int i = 0; i < codeChars.length; i++) {
            int height = 25;

            images[i] = new BufferedImage(height, height, 1);
            Graphics2D g2d = images[i].createGraphics();

            images[i] = g2d.getDeviceConfiguration().createCompatibleImage(height, height, 3);
            g2d.dispose();
            g2d = images[i].createGraphics();

            String sFont = "Arial";
            int iFontSize = random.nextInt(iMaxFontSize) % (iMaxFontSize - iMinFontSize + 1) + iMinFontSize;
            Font font = new Font(sFont, 0, iFontSize);
            g2d.setFont(font);
            g2d.setColor(fgColor);

            AffineTransform affineTransform = new AffineTransform();
            double scaleX = getRandomInRange(this.scaleRange);
            double scaleY = getRandomInRange(this.scaleRange);
            affineTransform.scale(scaleX, scaleY);

            double rotateX = getRandomInRange(this.rotateRange);

            affineTransform.rotate(rotateX, 15.0D, 15.0D);
            g2d.setTransform(affineTransform);
            FontRenderContext fontRenderContext = g2d.getFontRenderContext();

            int p_x = (height - g2d.getFontMetrics().charWidth(codeChars[i])) / 2;
            int p_y = (int)(height / 2 + font.getLineMetrics(Character.toString(codeChars[i]),
                    fontRenderContext).getAscent() / 2.0F);
            g2d.drawString(Character.toString(codeChars[i]), p_x, p_y);
            g2d.dispose();
        }

        return assembleImages(images);
    }

    private static Color GetRandColor(int iFC, int iBC) {
        int l_iMax = Math.max(iFC, iBC);
        int l_iMin = Math.min(iFC, iBC);

        if (l_iMin == l_iMax) {
            return new Color(l_iMin, l_iMin, l_iMin);
        }

        l_iMax -= l_iMin - 1;

        return new Color(
                l_iMin + CreateRandom(l_iMax),
                l_iMin + CreateRandom(l_iMax),
                l_iMin + CreateRandom(l_iMax));
    }

    private static int CreateRandom(int iSeed) {
        int l_iValue = 0;
        byte[] l_pbtRand = new byte[4];

        g_oRandom.nextBytes(l_pbtRand);

        for (int ii = 0; ii < 4; ii++) {
            byte l_btTmp = l_pbtRand[ii];
            l_iValue += ((l_btTmp & 0xFF) << 8 * ii);
        }

        l_pbtRand = (byte[])null;
        return Math.abs(l_iValue % iSeed);
    }

    private double getRandomInRange(double[] range) {
        if ((range == null) || (range.length != 2)) {
            throw new RuntimeException("范围参数不合法，必须包含两个元素");
        }
        return Math.random() * (range[1] - range[0]) + range[0];
    }

    private BufferedImage assembleImages(BufferedImage[] images) {
        int width = 70 + 3 * iDistance;
        int height = 25;

        BufferedImage bgImage = new BufferedImage(width, height, 4);
        Graphics2D g2d = bgImage.createGraphics();
        g2d.fillRect(0, 0, width, height);

        g2d.setColor(new Color(this.frameColor));
        g2d.drawRect(0, 0, width, height);

        int index = 0;
        int drawX = 5;
        int distance = 0;

        if ((images != null) && (images.length != 0)) {
            g2d.drawImage(images[index], drawX, 2, images[index].getWidth(), images[0].getHeight(), null);
            distance = calculateDistanceBetweenChar2(images[0], images[1]);
            drawX = drawX + images[index].getWidth() - distance + iDistance;
            index++;
        }

        while (index < images.length) {
            g2d.drawImage(images[index], drawX, 2, images[index].getWidth(), images[0].getHeight(), null);
            distance = calculateDistanceBetweenChar2(images[(index - 1)], images[index]);
            drawX = drawX + images[index].getWidth() - distance + iDistance;
            index++;
        }

        g2d.dispose();
        return bgImage;
    }

    private int calculateDistanceBetweenChar2(BufferedImage leftImage, BufferedImage rightImage) {
        int[][] left = calculateBlankNum(leftImage);

        int[][] right = calculateBlankNum(rightImage);

        int[] tempArray = new int[leftImage.getHeight()];
        for (int i = 0; i < left.length; i++) {
            if (right[i][0] == 0)
                tempArray[i] = (left[i][1] + leftImage.getWidth());
            else {
                tempArray[i] = (left[i][1] + right[i][0]);
            }
        }
        return min(tempArray);
    }

    private int[][] calculateBlankNum(BufferedImage image) {
        int width = image.getWidth();
        int height = image.getHeight();

        int[][] result = new int[height][2];
        for (int i = 0; i < result.length; i++) {
            result[i][0] = 0;
            result[i][1] = width;
        }

        int[] colorArray = new int[4];

        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                colorArray = image.getRaster().getPixel(j, i, colorArray);
                if (!checkArray(colorArray, new int[4])) {
                    if (result[i][0] == 0) {
                        result[i][0] = j;
                    }
                    result[i][1] = (width - j - 1);
                }
            }
        }

        return result;
    }

    private int min(int[] array) {
        int result = 2147483647;
        for (int i = 0; i < array.length; i++) {
            if (array[i] < result) {
                result = array[i];
            }
        }
        return result;
    }

    private boolean checkArray(int[] arrayA, int[] arrayB) {
        if ((arrayA == null) || (arrayB == null)) {
            return false;
        }
        if (arrayA.length != arrayB.length) {
            return false;
        }
        for (int i = 0; i < arrayA.length; i++) {
            if (arrayA[i] != arrayB[i]) {
                return false;
            }
        }
        return true;
    }
}
